using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Text;
using System.Transactions;
using System.Reflection;
using System.Configuration;

using NBear.Common;
using NBear.Data;

namespace NBear.Data
{
    public enum ActiveEntityStatus
    {
        New,
        Loaded,
    }

    public class BaseActiveEntity
    {
        protected static Dictionary<Type, List<Type>> extendedCascadeDeleteMap = new Dictionary<Type, List<Type>>();

        static BaseActiveEntity()
        {
            ComputeExtendedCascadeEntityDeleteMap();
        }

        private static void ComputeExtendedCascadeEntityDeleteMap()
        {
            Assembly[] asses = AppDomain.CurrentDomain.GetAssemblies();
            try
            {
                foreach (Assembly ass in asses)
                {
                    if (!ass.GetName().Name.StartsWith(BaseEntityFactory.DYNAMIC_ENTITY_NAMESPACE))
                    {
                        try
                        {
                            foreach (Type type in ass.GetTypes())
                            {
                                if (type.IsInterface && typeof(IEntity).IsAssignableFrom(type) && typeof(IEntity) != type && (!IsContractEntity(type)))
                                {
                                    ParseCascadeEntityDeleteMapFromType(type);
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch
            {
            }
        }

        private static void ParseCascadeEntityDeleteMapFromType(Type type)
        {
            Type[] interfaceTypes = type.GetInterfaces();

            if (interfaceTypes != null && interfaceTypes.Length > 0)
            {
                foreach (Type interfaceType in interfaceTypes)
                {
                    if (typeof(IEntity).IsAssignableFrom(interfaceType) && typeof(IEntity) != interfaceType && (!IsContractEntity(interfaceType)))
                    {
                        lock (extendedCascadeDeleteMap)
                        {
                            if (extendedCascadeDeleteMap.ContainsKey(interfaceType))
                            {
                                if (!extendedCascadeDeleteMap[interfaceType].Contains(type))
                                {
                                    extendedCascadeDeleteMap[interfaceType].Add(type);
                                }
                                else
                                {
                                    return;
                                }
                            }
                            else
                            {
                                List<Type> ls = new List<Type>();
                                ls.Add(type);
                                extendedCascadeDeleteMap.Add(interfaceType, ls);
                            }
                        }
                    }
                }
            }
        }

        public static bool IsContractEntity(Type type)
        {
            TableAttribute t = (GetEntityConfiguration(type) == null ? null : GetEntityConfiguration(type).Table);

            object[] attrs = (t == null ? type.GetCustomAttributes(typeof(TableAttribute), false) : new object[] { t });

            if (attrs != null && attrs.Length > 0)
            {
                TableAttribute ta = (TableAttribute)attrs[0];

                return ta.IsContract;
            }

            return false;
        }

        public static EntityConfiguration GetEntityConfiguration(Type t)
        {
            object section = ConfigurationManager.GetSection("EntityConfiguration");

            if (section == null)
            {
                return null;
            }

            if (typeof(System.Xml.XmlNodeList).IsAssignableFrom(section.GetType()))
            {
                section = new EntityConfigurationSection().Create(null, null, ((System.Xml.XmlNodeList)section)[0]);
            }

            if (section.GetType() == typeof(EntityConfigurationCollection))
            {
                return ((EntityConfigurationCollection)section)[t.FullName];
            }

            return null;
        }
    }

    public class ActiveEntity<IEntityType> : BaseActiveEntity
        where IEntityType : IEntity
    {
        #region Protected Memebers

        protected ActiveEntityStatus status = ActiveEntityStatus.New;

        protected IEntityType originalVersion
        {
            get
            {
                return EntityFactory<IEntityType>.CreateObject(workVersion.GetOriginalKeyValues());
            }
        }
        protected IEntityType workVersion;

        protected Gateway gateway;

        protected virtual void Load(IEntityType obj)
        {
            //workVersion = EntityFactory<IEntityType>.EntityClone(obj);
            workVersion = obj;
        }

        protected string BuildGetSql(string[] pkNames)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string pkName in pkNames)
            {
                if (pkName != pkNames[0])
                {
                    sb.Append(" and ");
                }
                sb.Append(gateway.BuildDbColumnName(pkName));
                sb.Append(" = ");
                sb.Append(gateway.BuildDbParamName(pkName));
            }

            return sb.ToString();
        }

        protected static MethodInfo GetGatewayMethodInfo(string signiture)
        {
            MethodInfo mi = null;
            foreach (MethodBase mb in typeof(Gateway).GetMethods())
            {
                if (mb.ToString() == signiture)
                {
                    mi = (MethodInfo)mb;
                    break;
                }
            }
            return mi;
        }

        #endregion

        #region Properties

        public ActiveEntityStatus Status
        {
            get
            {
                return status;
            }
        }

        public IEntityType Entity
        {
            get
            {
                return workVersion;
            }
        }

        #endregion

        #region Constructors

        public ActiveEntity() : this(null)
        {
        }

        protected ActiveEntity(Gateway gateway)
        {
            if (gateway == null)
            {
                this.gateway = Gateway.Default;
            }
            else
            {
                this.gateway = gateway;
            }
        }

        public ActiveEntity(Gateway gateway, IEntityType obj, ActiveEntityStatus loadedStatus) 
            : this(gateway)
        {
            Load(obj);

            this.status = loadedStatus;
        }

        public ActiveEntity(IEntityType obj, ActiveEntityStatus loadedStatus)
            : this(Gateway.Default, obj, loadedStatus)
        {
        }

        public ActiveEntity(Gateway gateway, params object[] pkValues)
            : this(gateway)
        {
            IEntityType obj = default(IEntityType);

            if (pkValues != null && pkValues.Length > 0)
            {
                string[] pkNames = Entity<IEntityType>.GetPrimaryKeyColumnNames();

                if (pkNames != null && pkNames.Length == pkValues.Length)
                {
                    obj = gateway.Get<IEntityType>(BuildGetSql(pkNames), pkValues);
                }
                else
                {
                    throw new NotSupportedException("ActiveEntity only supports entities having at least one primary key!");
                }
            }

            if (obj == null)
            {
                obj = EntityFactory<IEntityType>.CreateObject();
            }
            else
            {
                status = ActiveEntityStatus.Loaded;
            }
            
            Load(obj);
        }

        #endregion

        #region Public Operations

        public virtual void Save()
        {
            Save(null);
        }

        public virtual void Save(DbTransaction tran)
        {
            //do saving
            Type[] interfaces = typeof(IEntityType).GetInterfaces();
            string[] exceptColumns = EntityFactory<IEntityType>.CompareEntities(workVersion, originalVersion, true, true, false);
            List<string> tables = new List<string>();

            if (tran == null && gateway.Db.DbProvider.SupportADO20Transaction)
            {
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
                {
                    if (!(Entity<IEntityType>.IsView() && Entity<IEntityType>.GetUpdateTableName() == null))
                    {
                        if (this.status == ActiveEntityStatus.Loaded)
                        {
                            gateway.Update<IEntityType>(workVersion, exceptColumns);
                        }
                        else
                        {
                            gateway.Insert<IEntityType>(workVersion, exceptColumns);

                            tables.Add(Entity<IEntityType>.GetUpdateTableName() ?? Entity<IEntityType>.GetTableName() ?? typeof(IEntityType).Name);
                        }
                    }

                    if (Entity<IEntityType>.IsCascadeUpdate())
                    {
                        foreach (Type type in interfaces)
                        {
                            if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                            {
                                object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                                TableAttribute ta = (TableAttribute)attrs[0];
                                string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                                if (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null))))
                                {
                                    MethodInfo mi;
                                    if (this.status == ActiveEntityStatus.Loaded || tables.Contains(tmpTableName))
                                    {
                                        mi = GetGatewayMethodInfo("Void Update[IEntityType](IEntityType, System.String[])");
                                    }
                                    else
                                    {
                                        mi = GetGatewayMethodInfo("Int32 Insert[IEntityType](IEntityType, System.String[])");
                                        tables.Add(tmpTableName);
                                    }
                                    mi.MakeGenericMethod(type).Invoke(gateway, new object[] { workVersion, exceptColumns });
                                }
                            }
                        }
                    }

                    scope.Complete();
                }
            }
            else
            {
                DbTransaction t = (tran == null ? gateway.BeginTransaction() : tran);
                try
                {
                    if (!(Entity<IEntityType>.IsView() && Entity<IEntityType>.GetUpdateTableName() == null))
                    {
                        if (this.status == ActiveEntityStatus.Loaded)
                        {
                            gateway.Update<IEntityType>(workVersion, t, exceptColumns);
                        }
                        else
                        {
                            gateway.Insert<IEntityType>(workVersion, t, exceptColumns);

                            tables.Add(Entity<IEntityType>.GetUpdateTableName() ?? Entity<IEntityType>.GetTableName() ?? typeof(IEntityType).Name);
                        }
                    }

                    if (Entity<IEntityType>.IsCascadeUpdate())
                    {
                        foreach (Type type in interfaces)
                        {
                            if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                            {
                                object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                                TableAttribute ta = (TableAttribute)attrs[0];
                                string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                                if (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null))))
                                {
                                    MethodInfo mi;
                                    if (this.status == ActiveEntityStatus.Loaded || tables.Contains(tmpTableName))
                                    {
                                        mi = GetGatewayMethodInfo("Void Update[IEntityType](IEntityType, System.Data.Common.DbTransaction, System.String[])");
                                    }
                                    else
                                    {
                                        mi = GetGatewayMethodInfo("Int32 Insert[IEntityType](IEntityType, System.Data.Common.DbTransaction, System.String[])");

                                        tables.Add(tmpTableName);
                                    }
                                    mi.MakeGenericMethod(type).Invoke(gateway, new object[] { workVersion, t, exceptColumns });
                                }
                            }
                        }
                    }

                    if (tran == null)
                    {
                        t.Commit();
                    }
                }
                catch
                {
                    if (tran == null)
                    {
                        t.Rollback();
                    }
                    throw;
                }
                finally
                {
                    if (tran == null)
                    {
                        gateway.CloseTransaction(t);
                    }
                }
            }

            //update status
            workVersion.UpdateOriginalKeyValues();
            status = ActiveEntityStatus.Loaded;
        }

        public virtual void Delete()
        {
            Delete(null);
        }

        public virtual void Delete(DbTransaction tran)
        {
            if (this.status == ActiveEntityStatus.New)
            {
                return;
            }

            //do deleting
            Type[] interfaces = typeof(IEntityType).GetInterfaces();
            string[] pkNames = Entity<IEntityType>.GetPrimaryKeyColumnNames();
            object[] pkValues = (pkNames != null && pkNames.Length > 0 ? workVersion.GetKeyValues().GetValues(pkNames) : null);
            List<string> tables = new List<string>();

            if (tran == null && gateway.Db.DbProvider.SupportADO20Transaction)
            {
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
                {
                    if (!(Entity<IEntityType>.IsView() && Entity<IEntityType>.GetUpdateTableName() == null))
                    {
                        gateway.Delete<IEntityType>(BuildGetSql(pkNames), pkValues);
                        tables.Add(Entity<IEntityType>.GetUpdateTableName() ?? Entity<IEntityType>.GetTableName());
                    }

                    foreach (Type type in interfaces)
                    {
                        if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                        {
                            object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                            TableAttribute ta = (TableAttribute)attrs[0];
                            string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                            if ((!tables.Contains(tmpTableName)) && (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null)))))
                            {
                                string[] names = (string[])typeof(Entity<>).MakeGenericType(type).GetMethod("GetPrimaryKeyColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
                                object[] values = workVersion.GetKeyValues().GetValues(names);
                                GetGatewayMethodInfo("Void Delete[IEntityType](System.String, System.Object[])").MakeGenericMethod(type).Invoke(gateway, new object[] { BuildGetSql(names), values });

                                tables.Add(tmpTableName);
                            }
                        }
                    }

                    if (extendedCascadeDeleteMap.ContainsKey(typeof(IEntityType)))
                    {
                        foreach (Type type in extendedCascadeDeleteMap[typeof(IEntityType)])
                        {
                            if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                            {
                                object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                                TableAttribute ta = (TableAttribute)attrs[0];
                                string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                                if ((!tables.Contains(tmpTableName)) && (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null)))))
                                {
                                    string[] names = (string[])typeof(Entity<>).MakeGenericType(type).GetMethod("GetPrimaryKeyColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
                                    object[] values = workVersion.GetKeyValues().GetValues(names);
                                    GetGatewayMethodInfo("Void Delete[IEntityType](System.String, System.Object[])").MakeGenericMethod(type).Invoke(gateway, new object[] { BuildGetSql(names), values });

                                    tables.Add(tmpTableName);
                                }
                            }
                        }
                    }

                    scope.Complete();
                }
            }
            else
            {
                DbTransaction t = (tran == null ? gateway.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted) : tran);
                try
                {
                    if (!(Entity<IEntityType>.IsView() && Entity<IEntityType>.GetUpdateTableName() == null))
                    {
                        gateway.Delete<IEntityType>(BuildGetSql(pkNames), pkValues, t);

                        tables.Add(Entity<IEntityType>.GetUpdateTableName() ?? Entity<IEntityType>.GetTableName());
                    }

                    foreach (Type type in interfaces)
                    {
                        if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                        {
                            object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                            TableAttribute ta = (TableAttribute)attrs[0];
                            string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                            if ((!tables.Contains(tmpTableName)) && (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null)))))
                            {
                                string[] names = (string[])typeof(Entity<>).MakeGenericType(type).GetMethod("GetPrimaryKeyColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
                                object[] values = workVersion.GetKeyValues().GetValues(names);
                                GetGatewayMethodInfo("Void Delete[IEntityType](System.String, System.Object[], System.Data.Common.DbTransaction)").MakeGenericMethod(type).Invoke(gateway, new object[] { BuildGetSql(names), values, t });

                                tables.Add(tmpTableName);
                            }
                        }
                    }

                    if (extendedCascadeDeleteMap.ContainsKey(typeof(IEntityType)))
                    {
                        foreach (Type type in extendedCascadeDeleteMap[typeof(IEntityType)])
                        {
                            if (type != typeof(IEntity) && typeof(IEntity).IsAssignableFrom(type))
                            {
                                object[] attrs = type.GetCustomAttributes(typeof(TableAttribute), false);
                                TableAttribute ta = (TableAttribute)attrs[0];
                                string tmpTableName = ta.UpdateTableName ?? ta.Name ?? type.Name;

                                if ((!tables.Contains(tmpTableName)) && (attrs == null || (attrs != null && (!(ta.IsView && ta.UpdateTableName == null)))))
                                {
                                    string[] names = (string[])typeof(Entity<>).MakeGenericType(type).GetMethod("GetPrimaryKeyColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
                                    object[] values = workVersion.GetKeyValues().GetValues(names);
                                    GetGatewayMethodInfo("Void Delete[IEntityType](System.String, System.Object[], System.Data.Common.DbTransaction)").MakeGenericMethod(type).Invoke(gateway, new object[] { BuildGetSql(names), values, t });

                                    tables.Add(tmpTableName);
                                }
                            }
                        }
                    }

                    if (tran == null)
                    {
                        t.Commit();
                    }
                }
                catch
                {
                    if (tran == null)
                    {
                        t.Rollback();
                    }
                    throw;
                }
                finally
                {
                    if (tran == null)
                    {
                        gateway.CloseTransaction(t);
                    }
                }
            }

            //reset status
            workVersion = EntityFactory<IEntityType>.CreateObject();
            status = ActiveEntityStatus.New;
        }

        private string ParseTableName(Type type)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }
}
